﻿using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;

namespace NTrace
{
  internal class NTraceAstVisitor : AbstractAstVisitor
  {
    private const String MessageGuidVariableName = "_etwMessageGuid";
    private const String GetHashCodeMethodName = "GetHashCode";
    private PreprocessorContext context;

    public NTraceAstVisitor(PreprocessorContext context)
    {
      this.context = context;
    }

    public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
    {
      // TODO: We're being lazy here and always adding this member. It would be
      // better if we scanned the type first to see if we really needed to add it.
      if (
        typeDeclaration.Type == ClassType.Class ||
        typeDeclaration.Type == ClassType.Module ||
        typeDeclaration.Type == ClassType.Struct)
      {
        this.AddMessageGuidMember(typeDeclaration);
      }
      return base.VisitTypeDeclaration(typeDeclaration, data);
    }

    public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data)
    {
      foreach (VariableDeclaration variable in fieldDeclaration.Fields)
      {
        String fullyQualifiedPath = NTraceAstVisitor.GetFullyQualifiedPath(variable, fieldDeclaration.Parent as TypeDeclaration);
        if (this.context.Environment.EtwProviderVariable.CompareTo(fullyQualifiedPath) == 0)
        {
          // Capture the current location of the field declaration for replacement purposes
          Location startLocation = fieldDeclaration.StartLocation;
          Location endLocation = fieldDeclaration.EndLocation;

          String providerIdString = this.context.Environment.ProviderId.ToString("D");
          ObjectCreateExpression providerIdExpression = new ObjectCreateExpression(
            new TypeReference("Guid"),
            new List<Expression>(new Expression[] { new PrimitiveExpression(providerIdString, providerIdString) }));
          
          TypeReference fieldType = fieldDeclaration.TypeReference;
          if (fieldType.IsNull)
          {
            fieldType = fieldDeclaration.Fields[0].TypeReference;
          }
          
          variable.Initializer = new ObjectCreateExpression(
            fieldType,
            new List<Expression>(new Expression[] { providerIdExpression }));

          ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = this.context.Language.CreatePrinter();
          fieldDeclaration.AcceptVisitor(printer, null);
          String replacement = CodeUtilities.RemoveLineSeparators(printer.Text);
          NTraceAstVisitor.ReplaceRange(
            context,
            fieldDeclaration,
            replacement, 
            startLocation.Line - 1,
            startLocation.Column - 1,
            endLocation.Line - 1,
            endLocation.Column - 1,
            false);
        }
      }

      return base.VisitFieldDeclaration(fieldDeclaration, data);
    }

    public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data)
    {
      var output = base.VisitExpressionStatement(expressionStatement, data);
      InvocationExpression invocationExpression = expressionStatement.Expression as InvocationExpression;
      if (invocationExpression != null)
      {
        // First, check to see if it's a reference to a member named "Trace" (or whatever the configured environment is using)...
        MemberReferenceExpression traceCall = invocationExpression.TargetObject as MemberReferenceExpression;
        if (traceCall != null && String.Compare(traceCall.MemberName, this.context.Environment.Configuration.Macros.Trace, StringComparison.InvariantCulture) == 0)
        {
          // Next, check to see if it's a member on the trace class
          IdentifierExpression virtualClass = traceCall.TargetObject as IdentifierExpression;
          if (
            virtualClass != null && 
            String.Compare(virtualClass.Identifier, this.context.Environment.TraceClassName, StringComparison.InvariantCulture) == 0)
          {
            MemberNode parent = null;
            for ( INode currentNode = expressionStatement.Parent;
              parent == null;
              parent = currentNode as MemberNode, currentNode = currentNode.Parent);
            // Ladies and gentlemen, we have a call to our trace macro.
            this.ProcessTraceCall(parent, context, expressionStatement, invocationExpression);
          }
        }
      }
      return output;
    }

    public override object VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data)
    {
      IdentifierExpression identifierExpression = memberReferenceExpression.TargetObject as IdentifierExpression;
      // Is it the trace pseudo class?
      if (identifierExpression != null && String.Compare(identifierExpression.Identifier, this.context.Environment.TraceClassName, StringComparison.InvariantCulture) == 0)
      {
        String replacementStatement = null;

        Location startLocation = memberReferenceExpression.StartLocation;
        Location endLocation = memberReferenceExpression.EndLocation;
        // Is it a check against the Enabled property?
        if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.EnabledCheck, StringComparison.InvariantCulture) == 0)
        {
          // BUGBUG: I am going to hell for this.
          // As of version 3.0.0.3437, the VB.NET parser doesn't pick up the location information,
          // so we have to manufacture it.
          if (startLocation.IsEmpty && endLocation.IsEmpty)
          {
            startLocation = memberReferenceExpression.TargetObject.StartLocation;
            endLocation = new Location(
              memberReferenceExpression.TargetObject.EndLocation.Column + 1 + memberReferenceExpression.MemberName.Length,
              startLocation.Line);
          }

          // It's a check against the Enabled property. Time to replace the target object.
          memberReferenceExpression.TargetObject = this.context.Environment.GetTraceProviderReference();
          ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
          memberReferenceExpression.AcceptVisitor(printer, null);

          replacementStatement = printer.Text;
        }

        if (!String.IsNullOrEmpty(replacementStatement))
        {
          NTraceAstVisitor.ReplaceRange(
            context,
            memberReferenceExpression,
            replacementStatement,
            startLocation.Line - 1,
            startLocation.Column - 1,
            endLocation.Line - 1,
            endLocation.Column - 1,
            false);
        }
      }
      return base.VisitMemberReferenceExpression(memberReferenceExpression, data);
    }

    public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data)
    {
      Expression replacementExpression = null;

      MemberReferenceExpression memberReferenceExpression = invocationExpression.TargetObject as MemberReferenceExpression;
      if (memberReferenceExpression != null)
      {
        IdentifierExpression identifierExpression = memberReferenceExpression.TargetObject as IdentifierExpression;

        // Is it the trace pseudo class?
        if (identifierExpression != null && identifierExpression.Identifier.CompareTo(this.context.Environment.TraceClassName) == 0)
        {
          if (invocationExpression.Arguments.Count == 1)
          {
            MemberReferenceExpression enumReferenceExpression = invocationExpression.Arguments[0] as MemberReferenceExpression;

            if (enumReferenceExpression != null)
            {
              IdentifierExpression enumIdentifierExpression = enumReferenceExpression.TargetObject as IdentifierExpression;

              // Is it a check against the Flags property?
              if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceFlagCheck, StringComparison.InvariantCulture) == 0)
              {
                if (enumIdentifierExpression.Identifier == context.Environment.TraceFlagName)
                {
                  int flag = context.Environment.Configuration.TraceFlags[enumReferenceExpression.FieldName];

                  replacementExpression = new BinaryOperatorExpression(
                    this.context.Environment.GetTraceEnabledCheck(),
                    BinaryOperatorType.LogicalAnd,
                    this.context.Environment.GetTraceFlagLevelCheck(flag, 0));
                }
              }

              // Is it a check against the Level property?
              if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceLevelCheck, StringComparison.InvariantCulture) == 0)
              {
                if (enumIdentifierExpression.Identifier == context.Environment.TraceLevelName)
                {
                  int level = context.Environment.Configuration.TraceLevels[enumReferenceExpression.FieldName];

                  replacementExpression = new BinaryOperatorExpression(
                    this.context.Environment.GetTraceEnabledCheck(),
                    BinaryOperatorType.LogicalAnd,
                    this.context.Environment.GetTraceLevelCheck(level));
                }
              }
            }
          }
          else if (invocationExpression.Arguments.Count == 2)
          {
            MemberReferenceExpression flagEnumReferenceExpression = invocationExpression.Arguments[0] as MemberReferenceExpression;
            MemberReferenceExpression levelEnumReferenceExpression = invocationExpression.Arguments[1] as MemberReferenceExpression;

            // Is it a check against FlagAndLevelEnabled?
            if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceFlagLevelCheck, StringComparison.InvariantCulture) == 0)
            {
              // Let's verify that the inputs are what we expect
              IdentifierExpression flagEnumIdentifierExpression = flagEnumReferenceExpression.TargetObject as IdentifierExpression;
              IdentifierExpression levelEnumIdentifierExpression = levelEnumReferenceExpression.TargetObject as IdentifierExpression;

              if (flagEnumIdentifierExpression != null && levelEnumIdentifierExpression != null &&
                flagEnumIdentifierExpression.Identifier == context.Environment.TraceFlagName &&
                levelEnumIdentifierExpression.Identifier == context.Environment.TraceLevelName)
              {
                int flag = context.Environment.Configuration.TraceFlags[flagEnumReferenceExpression.MemberName];
                int level = context.Environment.Configuration.TraceLevels[levelEnumReferenceExpression.MemberName];
  
                  replacementExpression = new BinaryOperatorExpression(
                    this.context.Environment.GetTraceEnabledCheck(),
                    BinaryOperatorType.LogicalAnd,
                    this.context.Environment.GetTraceFlagLevelCheck(flag, level));
              }
              else
              {
                // Error condition
              }
            }
          }
        }
      }

      if (replacementExpression != null)
      {
        ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
        replacementExpression.AcceptVisitor(printer, null);
        String replacementStatement = printer.Text;

        NTraceAstVisitor.ReplaceRange(
          context,
          memberReferenceExpression,
          replacementStatement,
          invocationExpression.StartLocation.Line - 1,
          invocationExpression.StartLocation.Column - 1,
          invocationExpression.EndLocation.Line - 1,
          invocationExpression.EndLocation.Column - 1,
          false);
      }

      return base.VisitInvocationExpression(invocationExpression, data);
    }

    private void AddMessageGuidMember(TypeDeclaration typeDeclaration)
    {
      FieldDeclaration messageGuidMember = new FieldDeclaration(null);
      messageGuidMember.Modifier = Modifiers.Static | Modifiers.Private;
      ObjectCreateExpression initializer = new ObjectCreateExpression(
          new TypeReference("Guid"),
          new List<Expression>(new Expression[] { new PrimitiveExpression(context.TraceMessageFile.MessageGuid.ToString("D"), context.TraceMessageFile.MessageGuid.ToString("D")) }));
      VariableDeclaration messageGuid = new VariableDeclaration(
        NTraceAstVisitor.MessageGuidVariableName,
        initializer,
        new TypeReference("Guid"));
      messageGuidMember.Fields.Add(messageGuid);

      ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
      messageGuidMember.AcceptVisitor(printer, null);
      context.Lines.Insert(context.Offset + typeDeclaration.StartLocation.Line, context.Language.StartSourceWrapper(context.CodeFile, typeDeclaration.StartLocation.Line + 1));
      context.Lines.Insert(context.Offset + typeDeclaration.StartLocation.Line, CodeUtilities.RemoveLineSeparators(printer.Text));
      context.Lines.Insert(context.Offset + typeDeclaration.StartLocation.Line, context.Language.StopSourceWrapper());
      context.Offset += 3;
    }

    private static String GetFullyQualifiedPath(VariableDeclaration declaration, TypeDeclaration declaringType)
    {
      System.Text.StringBuilder buffer = new System.Text.StringBuilder();
      buffer.Append(declaration.Name);

      buffer.Insert(0, ".");
      buffer.Insert(0, declaringType.Name);

      INode parent = declaringType.Parent;
      while (parent != null)
      {
        TypeDeclaration typeParent = parent as TypeDeclaration;
        NamespaceDeclaration namespaceParent = parent as NamespaceDeclaration;
        if (typeParent != null)
        {
          buffer.Insert(0, ".");
          buffer.Insert(0, typeParent.Name);
        }
        else if (namespaceParent != null)
        {
          buffer.Insert(0, ".");
          buffer.Insert(0, namespaceParent.Name);
        }

        parent = parent.Parent;
      }

      return buffer.ToString();
    }
    
    private void ProcessTraceCall(MemberNode parent, PreprocessorContext context, Statement statement, InvocationExpression invocationExpression)
    {
      List<Expression> traceArguments = new List<Expression>(invocationExpression.Arguments);
      if (traceArguments.Count < 1) throw new PreprocessorException(500, "there should be at least one argument to the trace method.");

      // invocationExpression can have three variants: one with just a flag, and another with a flag and level
      MemberReferenceExpression fieldReferenceExpression = traceArguments[0] as MemberReferenceExpression;

      byte level = 0;
      int flag;
      if (fieldReferenceExpression == null)
      {
        // TODO: Make these reference configuration values!
        level = 4; // Information
        flag = 16; // Diagnostics
      }
      else
      {
        String enumerationName = NTraceAstVisitor.ExtractEnumerationName(traceArguments[0]);

        // Verify that the level enum in the code matches one of our configured enumerations
        if (this.context.Environment.TraceLevelName.CompareTo(enumerationName) == 0)
        {
          String levelString = NTraceAstVisitor.ExtractEnumerationValue(traceArguments[0]);
          if (!this.context.Environment.Configuration.TraceLevels.TryGetValue(levelString, out level))
          {
            throw new PreprocessorException(503, String.Format("invalid level value '{0}'.", levelString));
          }

          enumerationName = NTraceAstVisitor.ExtractEnumerationName(traceArguments[1]);
          if (this.context.Environment.TraceFlagName.CompareTo(enumerationName) != 0)
          {
            throw new PreprocessorException(504, String.Format("invalid flag enumeration '{0}'.", enumerationName));
          }

          String flagString = NTraceAstVisitor.ExtractEnumerationValue(invocationExpression.Arguments[1]);
          if (!this.context.Environment.Configuration.TraceFlags.TryGetValue(flagString, out flag))
          {
            throw new PreprocessorException(505, String.Format("invalid flag value '{0}'.", flagString));
          }

          // Snip off the first two arguments so that the format string and the arguments remain
          traceArguments.RemoveRange(0, 2);
        }
        else if (this.context.Environment.TraceFlagName.CompareTo(enumerationName) == 0)
        {
          // Verify that the flag enum in the code matches one of our configured enumerations
          String flagString = NTraceAstVisitor.ExtractEnumerationValue(invocationExpression.Arguments[0]);
          if (!this.context.Environment.Configuration.TraceFlags.TryGetValue(flagString, out flag))
          {
            throw new PreprocessorException(506, String.Format("invalid flag value '{0}'.", flagString));
          }

          // Snip off the first argument so that the format string and the arguments remain
          traceArguments.RemoveAt(0);
        }
        else
        {
          throw new PreprocessorException(507, String.Format("invalid enumeration '{0}'.", enumerationName));
        }
      }

      Statement traceCall = this.CreateTraceCall(parent, context, statement.StartLocation.Line, traceArguments, level, flag);

      // It's a call to the trace method. Wrap it with a conditional block.
      ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
      traceCall.AcceptVisitor(printer, null);
      String traceBlock = printer.Text;

      Expression check = this.context.Environment.GetTraceFlagLevelCheck(flag, level);
      printer = context.Language.CreatePrinter();
      check.AcceptVisitor(printer, null);
      String checkString = CodeUtilities.RemoveLineSeparators(printer.Text);

      // VBNETSUPPORT: This is an unfortunate hack; the AST model provided by NRefactory
      // doesn't allow us to either specify where the if block begins OR
      // where the block starts/ends.
      String replacementStatement = null;

      if (context.Language is CSharpLanguage)
      {
        replacementStatement = String.Format(
            "if ({0}) {{{1}}}",
            checkString,
            traceBlock);
      }
      else if (context.Language is VBNetLanguage)
      {
        replacementStatement = String.Format(
            "If {0} Then {1}",
            checkString,
            traceBlock);
      }
      else
      {
        // BUGBUG: Please change the above to accept the verbatim output of the printer
        // and update the offset
        throw new PreprocessorException(999, "This error should not have occurred.");
      }

      NTraceAstVisitor.ReplaceStatement(context, statement, replacementStatement);
    }

    private static void ReplaceStatement(PreprocessorContext context, Statement statement, String replacementStatement)
    {
      int firstStatementLine = statement.StartLocation.Line - 1;
      int firstStatementCharacter = statement.StartLocation.Column - 1;
      int lastStatementLine = statement.EndLocation.Line - 1;
      int lastStatementCharacter = statement.EndLocation.Column - context.Language.StatementEndOffset;

      NTraceAstVisitor.ReplaceRange(
        context,
        statement,
        replacementStatement,
        firstStatementLine,
        firstStatementCharacter,
        lastStatementLine,
        lastStatementCharacter,
        true);
    }

    private static void ReplaceRange(
      PreprocessorContext context,
      AbstractNode node,
      String replacementStatement,
      int firstStatementLine,
      int firstStatementCharacter,
      int lastStatementLine,
      int lastStatementCharacter,
      bool makeItFit)
    {
      String firstLine = context.Lines[context.Offset + firstStatementLine];
      String toAppend = null;
      if (lastStatementLine == firstStatementLine && firstLine.Length > lastStatementCharacter)
      {
        toAppend = firstLine.Substring(lastStatementCharacter);
      }
      context.Lines[context.Offset + firstStatementLine] = firstLine.Substring(0, firstStatementCharacter);
      
      int lineIndex = context.Offset + firstStatementLine;
      lineIndex++;
      while (lineIndex < lastStatementLine)
      {
        context.Lines[lineIndex] = String.Empty;
        lineIndex++;
      }
      if (context.Lines[context.Offset + lastStatementLine].Length >= lastStatementCharacter)
      {
        context.Lines[context.Offset + lastStatementLine] = context.Lines[context.Offset + lastStatementLine].Substring(lastStatementCharacter);
      }

      // for each line in the replacement statement, append the contents to the current line
      using (System.IO.StringReader reader = new System.IO.StringReader(replacementStatement))
      {
        lineIndex = context.Offset + firstStatementLine;
        String line;
        for (
          line = reader.ReadLine();
          line != null && lineIndex < context.Offset + lastStatementLine;
          line = reader.ReadLine(), lineIndex++)
        {
          context.Lines[lineIndex] += line;
        }
        if (line != null)
        {
          String remainder;
          if (makeItFit)
          {
            remainder = CodeUtilities.RemoveLineSeparators(line + reader.ReadToEnd());
          }
          else
          {
            remainder = line;
          }
          context.Lines[lineIndex] += remainder;
        }
      }

      context.Lines[context.Offset + lastStatementLine] += toAppend;
    }

    public Statement CreateTraceCall(
      MemberNode parent,
      PreprocessorContext context,
      int line,
      IList<Expression> args,
      byte level,
      int flag)
    {
      List<Expression> traceArguments = new List<Expression>(args);

      // Extract the format string, and format a TMF message for it
      PrimitiveExpression formatString = traceArguments[0] as PrimitiveExpression;

      // Snip off the first argument so that only the arguments remain
      traceArguments.RemoveAt(0);

      String formatStringContent = formatString.Value as String;
      System.Diagnostics.Debug.Assert(formatStringContent != null);

      String methodName = NTraceAstVisitor.GetFullName(parent);

      bool isStatic = (parent.Modifier & Modifiers.Static) == Modifiers.Static;
      // BUGBUG: This is here to accomodate VB.NET Parsing - modules should be treated as static
      if (!isStatic)
      {
        for(INode temp = parent.Parent;
          temp != null;
          temp = temp.Parent
          )
        {
          TypeDeclaration declaringType = temp as TypeDeclaration;
          if (declaringType != null)
          {
            if (declaringType.Type == ClassType.Module) isStatic = true;
            break;
          }
        }
      }

      IList<String> argumentNames = NTraceAstVisitor.ExtractTraceArgumentNames(context, traceArguments);

      int messageNumber = context.TraceMessageFile.AddMessage(
        line,
        formatStringContent,
        flag,
        level,
        argumentNames,
        methodName,
        isStatic);

      // Replace with a method that takes the message ID and any format strings

      Expression getHashCodeExpression = null;

      if (!isStatic)
      {
        // It's an instance method
        MemberReferenceExpression getHashCodeReference = new MemberReferenceExpression(
          new ThisReferenceExpression(),
          NTraceAstVisitor.GetHashCodeMethodName);
        getHashCodeExpression = new InvocationExpression(getHashCodeReference);
      }

      // The trace method signature is traceProvider.Trace(messageNumber, hashCode (zero for static), string params);
      List<Expression> arguments = new List<Expression>();

      MemberReferenceExpression messageGuidReference = new MemberReferenceExpression(
        new TypeReferenceExpression((parent.Parent as TypeDeclaration).Name),
        NTraceAstVisitor.MessageGuidVariableName);

      arguments.Add(messageGuidReference);
      arguments.Add(new PrimitiveExpression(messageNumber, messageNumber.ToString()));

      // If it's an instance method, add the GetHashCode() call.
      if (getHashCodeExpression != null)
      {
        arguments.Add(getHashCodeExpression);
      }

      for (int index = 0; index < traceArguments.Count; index++)
      {
        Expression traceArgument = traceArguments[index];
        PrimitiveExpression primitiveArg = traceArgument as PrimitiveExpression;
        if (primitiveArg != null && primitiveArg.Value is String)
        {
          arguments.Add(traceArgument);
        }
        else
        {
          MemberReferenceExpression toStringReference = new MemberReferenceExpression(
            traceArgument,
            "ToString");
          InvocationExpression toStringCall = new InvocationExpression(toStringReference);
          arguments.Add(toStringCall);
        }
      }

      MemberReferenceExpression methodCall = new MemberReferenceExpression(
        this.context.Environment.GetTraceProviderReference(),
        "Trace");
      InvocationExpression traceCall = new InvocationExpression(
        methodCall,
        arguments);
      return new ExpressionStatement(traceCall);
    }

    private static IList<String> ExtractTraceArgumentNames(PreprocessorContext context, List<Expression> traceArguments)
    {
      IList<String> output = new List<String>();
      foreach (Expression traceArgument in traceArguments)
      {
        ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();

        System.Diagnostics.Debug.Assert(printer != null, "Should have a non-null printer.");

        traceArgument.AcceptVisitor(printer, null);
        String name = printer.Text;
        System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(name), "Should have a non-null trace argument name.");

        output.Add(name);
      }

      return output;
    }

    private static string ExtractEnumerationName(Expression argument)
    {
      MemberReferenceExpression fieldReferenceExpression = argument as MemberReferenceExpression;
      if (fieldReferenceExpression == null) throw new PreprocessorException(501, "Invalid expression.");
      IdentifierExpression identifierExpression = fieldReferenceExpression.TargetObject as IdentifierExpression;
      if (identifierExpression == null) throw new PreprocessorException(501, "Invalid expression.");
      return identifierExpression.Identifier;
    }

    private static string ExtractEnumerationValue(Expression argument)
    {
      MemberReferenceExpression fieldReferenceExpression = argument as MemberReferenceExpression;
      if (fieldReferenceExpression == null) throw new PreprocessorException(501, "Invalid expression.");
      return fieldReferenceExpression.MemberName;
    }

    internal static String GetFullName(MemberNode member)
    {
      System.Diagnostics.Debug.Assert(member != null, "Expected a non-null method declaration.");
      if (member == null) throw new ArgumentNullException("methodDeclaration");
      System.Text.StringBuilder buffer = new System.Text.StringBuilder();

      TypeDeclaration typeDeclaration = member.Parent as TypeDeclaration;
      System.Diagnostics.Debug.Assert(typeDeclaration != null, "Expected a non-null type declaration.");

      buffer.AppendFormat(
        "{0}.{1}",
        typeDeclaration.Name,
        member.Name);

      for (INode parent = typeDeclaration.Parent; parent != null && !(parent is CompilationUnit); parent = parent.Parent)
      {
        TypeDeclaration typeParent = parent as TypeDeclaration;
        NamespaceDeclaration namespaceParent = parent as NamespaceDeclaration;

        if (typeParent != null)
        {
          buffer.Insert(0, ".");
          buffer.Insert(0, typeParent.Name);
        }
        if (namespaceParent != null)
        {
          buffer.Insert(0, ".");
          buffer.Insert(0, namespaceParent.Name);
        }
      }

      return buffer.ToString();
    }
  }
}
